package com.example.csmallpassport.service.impl;

import com.alibaba.fastjson.JSON;
import com.example.csmallpassport.ex.ServiceException;
import com.example.csmallpassport.mapper.AdminMapper;
import com.example.csmallpassport.pojo.dto.AdminAddNewDTO;
import com.example.csmallpassport.pojo.dto.AdminLoginDTO;
import com.example.csmallpassport.pojo.dto.AdminUpdateDTO;
import com.example.csmallpassport.pojo.entity.Admin;
import com.example.csmallpassport.pojo.vo.AdminListItemVO;
import com.example.csmallpassport.pojo.vo.AdminStandardVO;
import com.example.csmallpassport.security.AdminDetails;
import com.example.csmallpassport.service.AdminService;
import com.example.csmallpassport.web.ServiceCode;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
@Slf4j
public class AdminServiceImpl implements AdminService {

    /**
     * 此接口的对象是通过SecurityConfiguration配置类中
     * 重写authenticationManagerBean方法并且再方法上添加@Bean注解
     * 然后再通过@Autowired注解进行自动装配
     */
    @Autowired //自动装配AuthenticationManager接口
    private AuthenticationManager authenticationManager;

    @Autowired
    private AdminMapper adminMapper;

    @Value("${csmall.jwt.secret-key}")
    String secretKey;

    @Value("${csmall.jwt.duration-in-minute}")
    Integer durationInMinute;

    @Override
    public String login(AdminLoginDTO adminLoginDTO) {
        log.debug("在AdminServiceImpl中开始处理了【管理员登录】的业务，参数:{}", adminLoginDTO);

        Authentication authentication = new UsernamePasswordAuthenticationToken(
                adminLoginDTO.getUsername(), adminLoginDTO.getPassword()
        );
        log.debug("通过UsernamePasswordAuthenticationToken生成的Authentication对象信息:{}", authentication);

        /*
         * 调用authenticationManage.authenicate方法来自动调用重写了UserDetailsService接口的loadUserByUsername方法进行用户认证
         * authenticate方法的参数为 Authentication接口类型，此接口的对象需要通过new UsernamePasswordAuthenticationToken()类型进行获取
         * 并且UsernamePasswordAuthenticationToken类型没有无参的构造方法，需要传递两个参数(当事人.凭证)
         * 此处我们直接传入用户再客户端书如的用户名密码就可以了
         * 此方法的返回值为AuthentAction对象并且此对象中存放着用户的详细信息
         * */
        Authentication authenticationResult = authenticationManager.authenticate(authentication);
        log.debug("验证登录成功,返回的authenticationResult的信息为：{}", authenticationResult);

        /*
         * 通过SecurityContextHolder获取到SecurityContext对象，
         * */
        // SecurityContext context = SecurityContextHolder.getContext();
        // log.debug("即将向SecurityContext中存入Authentication");
        /*
         * 将上面认证成功后返回的Authentication用户详情对象 存放在SecurityContext对象当中
         * 当我们将认证成功的用户详情存放在SecurityContext对象当中后，此时在浏览器中向服务端发送数据，服务器就是接收请求的
         * */
        // context.setAuthentication(authenticationResult);

        //获取Authentication类型中代表着当事人(Principal)的信息
        AdminDetails principal = (AdminDetails) authenticationResult.getPrincipal();
        log.warn("在验证成功的authenticationResult返回值中获取到的(AdminDetails类型)当事人信息:{}", principal);
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", principal.getId());
        claims.put("username", principal.getUsername());
        String authoritiesJsonString = JSON.toJSONString(principal.getAuthorities());
        claims.put("authoritiesJsonString", authoritiesJsonString);
        log.debug("从当事人（principal(变量名)此变量为AdminDetails类型)中获取其id,username,authorities信息:{}", claims);

        //生成JWT，返回JWT
        Date date = new Date(System.currentTimeMillis() + durationInMinute * 60 * 1000);

        String jwt = Jwts.builder()
                .setHeaderParam("alg", "HS256")
                .setHeaderParam("type", "JMT")
                .setClaims(claims)
                .setExpiration(date)
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
        log.debug("通过当事人信息最后生成的jwt信息:{}", jwt);
        return jwt;
    }

    @Override
    public void addNew(AdminAddNewDTO adminAddNewDTO) {
        int i = adminMapper.countByUsername(adminAddNewDTO.getUsername());
        if (i != 0) {
            String message = "管理员名称已经存在,不允许重复";
            throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
        }
        i = adminMapper.countByPhone(adminAddNewDTO.getPhone());
        if (i != 0) {
            String message = "管理员手机号已经存在,不允许重复";
            throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
        }
        i = adminMapper.countByEmail(adminAddNewDTO.getEmail());
        if (i != 0) {
            String message = "管理员邮箱已经存在,不允许重复";
            throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
        }

        Admin admin = new Admin();
        adminAddNewDTO.setPassword(new BCryptPasswordEncoder().encode(adminAddNewDTO.getPassword()));
        BeanUtils.copyProperties(adminAddNewDTO, admin);

        int rows = adminMapper.insert(admin);
        if (rows != 1) {
            String message = "服务器在处理删除请求时出现异常";
            throw new ServiceException(ServiceCode.ERR_INSERT, message);
        }
    }

    @Override
    public void update(Long id, AdminUpdateDTO adminUpdateDTO) {
        AdminStandardVO standardById = adminMapper.getStandardById(id);
        if (standardById == null) {
            String message = "您想要修改的数据是不存在的！";
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }
        Admin admin = new Admin();
        adminUpdateDTO.setPassword(new BCryptPasswordEncoder().encode(adminUpdateDTO.getPassword()));
        BeanUtils.copyProperties(adminUpdateDTO, admin);
        admin.setId(id);
        adminMapper.update(admin);
    }

    @Override
    public void delete(Long id) {
        if (id == 1 || id == null) {
            String message = "您想要删除的管理员信息不存在";
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }
        int rows = adminMapper.deleteById(id);
        if (rows != 1) {
            String message = "服务器在处理删除请求时出现异常";
            throw new ServiceException(ServiceCode.ERR_DELETE, message);
        }
    }

    @Override
    public void enable(Long id) {
        int state = 1;
        updateState(id, state);
    }

    @Override
    public void disable(Long id) {
        int state = 0;
        updateState(id, state);
    }

    private void updateState(Long id, Integer state) {
        log.debug("id:{}", id);
        if (id == 1) {
            String message = "您想要启用的管理员信息不存在";
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }
        AdminStandardVO standardById = adminMapper.getStandardById(id);
        if (standardById.getEnable().equals(state)) {
            return;
        }
        Admin admin = new Admin();
        admin.setId(id);
        admin.setEnable(state);
        adminMapper.update(admin);
    }

    @Override
    public AdminStandardVO getStandardById(Long id) {
        if (id == 1) {
            String message = "您想要查询的管理员信息是不存在的";
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }
        AdminStandardVO standardById = adminMapper.getStandardById(id);
        if (standardById == null) {
            String message = "您想要查询的管理员信息是不存在的";
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }
        return standardById;
    }

    @Override
    public List<AdminListItemVO> list() {
        List<AdminListItemVO> list = adminMapper.list();
        AdminListItemVO a = null;
        for (AdminListItemVO adminListItemVO : list) {
            if (adminListItemVO.getId() == 1) {
                a = adminListItemVO;
            }
        }
        list.remove(a);
        return list;
    }

}
